package com.yuoj.yuojcodesandbox.sandbox.java;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.text.CharSequenceUtil;
import com.yuoj.yuojcodesandbox.CodeSandBox;
import com.yuoj.yuojcodesandbox.model.ExecuteCodeRequest;
import com.yuoj.yuojcodesandbox.model.ExecuteCodeResponse;
import com.yuoj.yuojcodesandbox.model.ExecuteMessage;
import com.yuoj.yuojcodesandbox.model.JudgeInfo;
import com.yuoj.yuojcodesandbox.utils.ProcessUtils;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;

/**
 * @author 刘渠好
 * @since 2025-03-19 21:28
 * java原生代码沙箱模版方法实现
 */
@Slf4j
public abstract class JavaNativeCodeSandboxTemple implements CodeSandBox {

    //存放代码文件夹
    public static final String  GLOBAL_CODE_DIR_NAME="codeTemp";

    //统一java文件名字
    public static final String GLOBAL_JAVA_CLASS_NAME="Main.java";

    //限制时间
    public static final Long LIMIT_MAX_TIME =10000L;  //10s

    //1.保存代码
    public File saveCode(String code){
        String userDir = System.getProperty ("user.dir");  //根路径
        String globalCodePathName=userDir+ File.separator+GLOBAL_CODE_DIR_NAME;
        //不存在则重建文件夹
        if (!FileUtil.exist (globalCodePathName)) {
            FileUtil.mkdir(globalCodePathName);
        }
        //用户代码分层存放
        String userCodeParentPath=globalCodePathName+File.separator+ UUID.randomUUID();
        //新建用户代码文件，将用户输入的代码写进java文件
        String userCodePath=userCodeParentPath+File.separator+GLOBAL_JAVA_CLASS_NAME;
        //java代码文件
       return FileUtil.writeString (code,userCodePath, StandardCharsets.UTF_8);
    }

    //2.编译代码
    public ExecuteMessage compileCode(File userCodeFile){
        String compileCmd = String.format ("javac -encoding utf-8 %s", userCodeFile.getAbsoluteFile ());
        try {
            Process compileProcess = Runtime.getRuntime ().exec (compileCmd);
            ExecuteMessage executeMessage = ProcessUtils.runProcessAndGetMessage (compileProcess, "编译");
            if (executeMessage.getExitValue ()!=0){
                throw new RuntimeException ("编译错误!");
            }
            return executeMessage;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    //3.执行代码
    public List<ExecuteMessage> runCode(List<String> input,File userCodeFile){
        List<ExecuteMessage> messages = new ArrayList<> ();
        for (String inputArg : input) {
            String runCmd = String.format ("java -cp %s -Dfile.encoding=UTF-8 Main %s", userCodeFile.getParentFile ().getAbsolutePath (), inputArg);
            try {
                Process runProcess = Runtime.getRuntime ().exec (runCmd);
                //守护线程来监测执行时间是否超时
                new Thread (()->{
                    try {
                        Thread.sleep (LIMIT_MAX_TIME);
                        if (runProcess.isAlive ()){
                            log.info ("超时了，中断进程!");
                            runProcess.destroy ();
                        }

                    } catch (InterruptedException e) {
                        throw new RuntimeException (e);
                    }

                }).start ();
                ExecuteMessage executeMessage = ProcessUtils.runProcessAndGetMessage (runProcess, "运行");
                log.info ("执行代码信息:{}",executeMessage);
                messages.add (executeMessage);
            } catch (IOException e) {
//                getErrorResponse (e);
                throw new RuntimeException("执行错误",e);
            }

        }
        return messages;
    }

    //4.整理输出结果
    public ExecuteCodeResponse orderResult(List<ExecuteMessage> messages){
        ExecuteCodeResponse executeCodeResponse = new ExecuteCodeResponse ();
        ArrayList<String> outputList = new ArrayList<> ();
        //取执行程序最长时间，判断是否超时
        long maxTime = 0;
        for (ExecuteMessage message : messages) {
            String errorMessage = message.getErrorMessage ();
            if (CharSequenceUtil.isNotBlank (errorMessage)) {
                //程序存在异常
                executeCodeResponse.setMessage (errorMessage);
                //设置状态码
                executeCodeResponse.setStatus (3);
                //退出，直接返回错误信息，不继续下去
                break;
            }
            outputList.add (message.getMessage ());
            Long time = message.getTime ();
            if (time != null) {
                maxTime = Math.max (maxTime, time);
            }
        }
        //执行正常
        if (outputList.size ()==messages.size()){
            executeCodeResponse.setStatus (1);
        }
        executeCodeResponse.setOutput (outputList);
        JudgeInfo judgeInfo = new JudgeInfo ();
        judgeInfo.setTime (maxTime);
        //内存实现比较麻烦，要借助第三方应用，暂不实现
//        judgeInfo.setMemory ();
        executeCodeResponse.setJudgeInfo (judgeInfo);
        return executeCodeResponse;

    }

    //5.清理文件
    public boolean delCodeFile(File userCodeFile){
        if (FileUtil.isNotEmpty (userCodeFile)){
            boolean del = FileUtil.del (userCodeFile.getParentFile ().getAbsolutePath ());
            log.info ("删除{}",(del ? "成功":"失败"));
            return del;
        }
        return true;
    }

    @Override
    public ExecuteCodeResponse executeCode(ExecuteCodeRequest request) {
        List<String> input = request.getInput ();
        String code = request.getCode ();

        //1.保存代码
        File codeFile = saveCode (code);

        //2.编译代码
        ExecuteMessage executeMessage = compileCode (codeFile);
        log.info ("{}",executeMessage);

        //3.执行代码
        List<ExecuteMessage> executeMessages = runCode (input, codeFile);

        //4.整理输出结果
        ExecuteCodeResponse executeCodeResponse = orderResult (executeMessages);

        //5.清理文件
        boolean b = delCodeFile (codeFile);
        if (!b){
            log.error ("deleteFile error,userCodePathFile={}", codeFile.getAbsolutePath ());
        }

        return executeCodeResponse;
    }

    //6.异常处理

    public ExecuteCodeResponse getErrorResponse (Throwable e) {
        ExecuteCodeResponse executeCodeResponse = new ExecuteCodeResponse ();
        executeCodeResponse.setMessage (e.getMessage ());
        executeCodeResponse.setOutput (new LinkedList<> ());
        executeCodeResponse.setJudgeInfo (new JudgeInfo ());
        executeCodeResponse.setStatus (2);
        return executeCodeResponse;

    }

}
